package edu.pku.sei.sct.evaluation;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;

import edu.pku.sei.AMT.Rule;
import edu.pku.sei.AMT.Transformation;
import edu.pku.sei.AMT.TypedModel;
import edu.pku.sei.AMT.Variable;
import edu.pku.sei.ocl.cst.HelperCallExpCS;
import edu.pku.sei.ocl.cst.OCLExpressionCS;
import edu.pku.sei.ocl.cst.SimpleNameCS;
import edu.pku.sei.ocl.evaluation.Context;
import edu.pku.sei.ocl.evaluation.Environment;
import edu.pku.sei.ocl.utilities.OCLConstant;
import edu.pku.sei.ocl.utilities.StandardLibraryUtil;
import edu.pku.sei.ocl.visitor.AbstractOCLVisitor;
import edu.pku.sei.ocl.visitor.impl.ExpressionCheckVisitor;
import edu.pku.sei.ocl.visitor.impl.ModelCheckVisitor;
import edu.pku.sei.ocl.visitor.impl.ModelEnforceVisitor;
import edu.pku.sei.ocl.visitor.impl.ModelPropagateVisitor;
import edu.pku.sei.sct.match.ModelManager;

public class TransformationEnvironment extends Environment {
	public TransformationEnvironment(Transformation t) {
		super();
		transformation = t;
		direction = new BasicEList<TypedModel>();
	}
	
	private EList<TypedModel> direction;
	public EList<TypedModel> getDirection() {
		return direction;
	}
	
	private SCTEnvironment sctEnvironment = null;
	
	public SCTEnvironment getSctEnvironment() {
		return sctEnvironment;
	}

	public void setSctEnvironment(SCTEnvironment sctEnvironment) {
		this.sctEnvironment = sctEnvironment;
	}

	private Transformation transformation;

	public Transformation getTransformation() {
		return transformation;
	}

	public void setTransformation(Transformation transformation) {
		this.transformation = transformation;
	}

	public Context createContext(Environment e) {
		RuleContext tc = new RuleContext(e);
		return tc;
	}
	@Override
	protected Object invokeStaticHelper(HelperCallExpCS callexp,
			AbstractOCLVisitor visitor, Context context) {
		String helperName = callexp.getHelperName().getValue();
		EList<OCLExpressionCS> arguments = callexp.getArguments();
		
		
		Object[] actualParams = new Object[arguments.size()];
		
		for(int i=0;i<actualParams.length;i++) {
			actualParams[i] = arguments.get(i).accept(visitor, context);
		}
		
		try {
			if(callexp.isDefaultAM()==false&&actualParams.length!=0)
				throw new Exception("when calling static helper, isDefaultAM must be true");
			
			return invokeStaticHelper(helperName, actualParams);
		} catch (Exception e) {
			// try invoke rule
			if(callexp.isDefaultAM()&&actualParams.length!=0) return OCLConstant.OCLInvalid;
			
			Rule rule = transformation.getRuleByName(helperName);
			RuleContext nc = (RuleContext) createContext(this);
			nc.setRule(rule);
			
			nc.setMode(((RuleContext) context).getMode());
			
			EList<SimpleNameCS> fpNames = callexp.getFpNames();
			
			for(int i = 0; i< actualParams.length;i++) {
				String fName = fpNames.get(i).getValue();
				Object aValue = actualParams[i];
				nc.putValue(fName, aValue);
			}
			
			String mode = nc.getMode();
			
			if(mode==null) { // recover default mode
				if(visitor == AbstractOCLVisitor.modelCheck) mode = "check";
				else if(visitor == AbstractOCLVisitor.modelPropagate) mode = "propagation";
				else if(visitor == AbstractOCLVisitor.modelEnforce) mode = "enforce";
				else if(visitor == AbstractOCLVisitor.expressionEnforce) mode = "imperative";
			}
			
			return sctEnvironment.invokeRuleEntry(mode, rule, nc, this);
		}
	}
	
	private TraceStore traceStore = new TraceStore();
	
	public boolean addTrace(RuleContext rc) {
		traceStore.addTrace(rc);
		return true;
	}
	
	public boolean removeTrace(RuleContext rc) {
		traceStore.removeTrace(rc);
		return true;
	}
	
	public boolean removeTrace(Rule r) {
		traceStore.removeRuleTraces(r);
		return true;
	}
	
	public List<RuleContext> getTraces() {
		return traceStore.allTraces;
	}
	
	public List<RuleContext> getTraces(Rule r) {
		return traceStore.getRuleTraces(r);
	}
	
	public EList<RuleContext> resolveTrace(RuleContext rc){
		EList<RuleContext> results = new BasicEList<RuleContext>();
		
		for(RuleContext c : traceStore.getRuleTraces(rc.getRule())) {
			if(rc.isIncludedBy(c)) {
				results.add(c);
			}
		}
		return results;
	}
	
	
}



class TraceStore {
	protected LinkedList<RuleContext> allTraces = new LinkedList<RuleContext>();
	protected HashMap<Rule,LinkedList<RuleContext>> ruleTraces = new HashMap<Rule,LinkedList<RuleContext>>();
	public void addTrace(RuleContext rc) {
		allTraces.add(rc);
		getRuleTraces(rc.getRule()).add(rc);
	}
	
	public List<RuleContext> getRuleTraces(Rule r) {
		List<RuleContext> l = ruleTraces.get(r);
		if(l==null) {
			l = new LinkedList<RuleContext>();
			ruleTraces.put(r, (LinkedList<RuleContext>) l);
		}
		
		return l;
	}
	
	public void removeTrace(RuleContext rc) {
		allTraces.remove(rc);
		getRuleTraces(rc.getRule()).remove(rc);
	}
	
	public void removeRuleTraces(Rule r) {
		List<RuleContext> rcs = getRuleTraces(r);
		allTraces.removeAll(rcs);
		rcs.clear();
	}
	public List<RuleContext> getTraces() {
		return allTraces;
	}
}